package dao

import (
	"context"
	"errors"
	"forum/intr/common"
	"forum/intr/domain"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

type InteractiveDao interface {
	GetById(ctx context.Context, biz string, id int64) (domain.Interactive, error)
	UpdateReadCnt(ctx context.Context, biz string, id int64) error
	InsertLikeInfo(ctx context.Context, biz string, id int64, uid int64) error
	DeleteLikeInfo(ctx context.Context, biz string, id int64, uid int64) error
	InsertCollectInfo(ctx context.Context, biz string, id int64, uid int64) error
	DeleteCollectInfo(ctx context.Context, biz string, id int64, uid int64) error
	GetLiked(ctx context.Context, biz string, id int64, uid int64) (bool, error)
	GetCollected(ctx context.Context, biz string, id int64, uid int64) (bool, error)
	BatchIncrReadCnt(ctx context.Context, bizs []string, ids []int64) error
}

type InteractiveMysqlDao struct {
	db *gorm.DB
}

func NewInteractiveMysqlDao(db *gorm.DB) *InteractiveMysqlDao {
	return &InteractiveMysqlDao{db: db}
}

func (i *InteractiveMysqlDao) BatchIncrReadCnt(ctx context.Context, bizs []string, ids []int64) error {
	return i.db.Transaction(func(tx *gorm.DB) error {
		txDao := NewInteractiveMysqlDao(tx)
		for i, biz := range bizs {
			err := txDao.UpdateReadCnt(ctx, biz, ids[i])
			if err != nil {
				//TODO log
				//TODO 重试策略
				return err
			}
		}
		return nil
	})
}

func (i *InteractiveMysqlDao) GetById(ctx context.Context, biz string, id int64) (domain.Interactive, error) {
	var res Interactive
	err := i.db.WithContext(ctx).
		Where("biz = ? AND biz_id = ?", biz, id).
		First(&res).Error
	toInter := i.InterDaoToDomain(res)
	return toInter, err
}

func (i *InteractiveMysqlDao) GetLiked(ctx context.Context, biz string, id int64, uid int64) (bool, error) {
	var res UserLikeBiz
	err := i.db.WithContext(ctx).
		Where("biz = ? AND biz_id = ? AND uid = ? AND status = ?",
			biz, id, uid, 1).
		First(&res).Error
	switch err {
	case nil:
		return true, nil
	case common.ErrErrRecordNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (i *InteractiveMysqlDao) GetCollected(ctx context.Context, biz string, id int64, uid int64) (bool, error) {
	var res UserCollectionBiz
	err := i.db.WithContext(ctx).
		Where("biz = ? AND biz_id = ? AND uid = ?",
			biz, id, uid).
		First(&res).Error
	switch err {
	case nil:
		return true, nil
	case common.ErrErrRecordNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (i *InteractiveMysqlDao) UpdateReadCnt(ctx context.Context, biz string, id int64) error {
	now := time.Now().UnixMilli()
	return i.db.WithContext(ctx).Clauses(clause.OnConflict{
		DoUpdates: clause.Assignments(map[string]interface{}{
			"read_cnt": gorm.Expr("`read_cnt` + 1"),
			"utime":    now,
		}),
	}).Create(&Interactive{
		Biz:     biz,
		BizId:   id,
		ReadCnt: 1,
		Ctime:   now,
		Utime:   now,
	}).Error
}

func (i *InteractiveMysqlDao) InsertLikeInfo(ctx context.Context, biz string, id int64, uid int64) error {
	now := time.Now().UnixMilli()
	var res UserLikeBiz
	err := i.db.WithContext(ctx).Where("biz_id = ? AND biz = ? AND uid = ?", id, biz, uid).First(&res).Error
	if !errors.Is(err, gorm.ErrRecordNotFound) && err != nil {
		return common.ErrRepeatLike
	}
	if res.Status == 1 {
		return common.ErrRepeatLike
	}
	return i.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Clauses(clause.OnConflict{
			DoUpdates: clause.Assignments(map[string]interface{}{
				"utime":  now,
				"status": 1,
			}),
		}).Create(&UserLikeBiz{
			Uid:    uid,
			Biz:    biz,
			BizId:  id,
			Status: 1,
			Utime:  now,
			Ctime:  now,
		}).Error
		if err != nil {
			return err
		}
		return tx.WithContext(ctx).Clauses(clause.OnConflict{
			DoUpdates: clause.Assignments(map[string]interface{}{
				"like_cnt": gorm.Expr("`like_cnt` + 1"),
				"utime":    now,
			}),
		}).Create(&Interactive{
			Biz:     biz,
			BizId:   id,
			LikeCnt: 1,
			Ctime:   now,
			Utime:   now,
		}).Error
	})
}

func (i *InteractiveMysqlDao) DeleteLikeInfo(ctx context.Context, biz string, id int64, uid int64) error {
	now := time.Now().UnixMilli()
	var res UserLikeBiz
	err := i.db.WithContext(ctx).Where("biz_id = ? AND biz = ? AND uid = ?", id, biz, uid).First(&res).Error
	if err != nil {
		return common.ErrRepeatLike
	}
	if res.Status == 1 {
		return common.ErrRepeatLike
	}
	return i.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Model(&UserLikeBiz{}).
			Where("uid=? AND biz_id = ? AND biz=?", uid, id, biz).
			Updates(map[string]interface{}{
				"utime":  now,
				"status": 0,
			}).Error
		if err != nil {
			return err
		}
		return tx.Model(&Interactive{}).
			Where("biz =? AND biz_id=?", biz, id).
			Updates(map[string]interface{}{
				"like_cnt": gorm.Expr("`like_cnt` - 1"),
				"utime":    now,
			}).Error
	})
}

func (i *InteractiveMysqlDao) InsertCollectInfo(ctx context.Context, biz string, id int64, uid int64) error {
	now := time.Now().UnixMilli()
	res := UserCollectionBiz{
		Uid:   uid,
		BizId: id,
		Biz:   biz,
		Cid:   0,
		Utime: now,
		Ctime: now,
	}
	err := i.db.WithContext(ctx).First(&res).Error
	if err == nil {
		return common.ErrRepeatCollect
	}
	return i.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Create(&res).Error
		if err != nil {
			return err
		}
		return tx.WithContext(ctx).Clauses(clause.OnConflict{
			DoUpdates: clause.Assignments(map[string]interface{}{
				"collect_cnt": gorm.Expr("`collect_cnt` + 1"),
				"utime":       now,
			}),
		}).Create(&Interactive{
			Biz:        biz,
			BizId:      id,
			CollectCnt: 1,
			Ctime:      now,
			Utime:      now,
		}).Error
	})
}

func (i *InteractiveMysqlDao) DeleteCollectInfo(ctx context.Context, biz string, id int64, uid int64) error {
	now := time.Now().UnixMilli()
	var res UserCollectionBiz
	err := i.db.WithContext(ctx).Where("biz_id = ? AND biz = ? AND uid = ?", id, biz, uid).First(&res).Error
	if err != nil {
		return common.ErrRepeatCollect
	}
	return i.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Model(&UserCollectionBiz{}).
			Delete(&res).Error
		if err != nil {
			return err
		}
		return tx.Model(&Interactive{}).
			Where("biz =? AND biz_id=?", biz, id).
			Updates(map[string]interface{}{
				"collect_cnt": gorm.Expr("`collect_cnt` - 1"),
				"utime":       now,
			}).Error
	})
}

type Interactive struct {
	Id int64 `gorm:"primaryKey,autoIncrement"`
	// <bizid, biz>
	BizId int64 `gorm:"uniqueIndex:biz_type_id"`
	// WHERE biz = ?
	Biz string `gorm:"type:varchar(128);uniqueIndex:biz_type_id"`

	ReadCnt    int64
	LikeCnt    int64
	CollectCnt int64
	Utime      int64
	Ctime      int64
}

func (i *InteractiveMysqlDao) InterDaoToDomain(interactive Interactive) domain.Interactive {
	return domain.Interactive{
		ReadCnt:    interactive.ReadCnt,
		LikeCnt:    interactive.LikeCnt,
		CollectCnt: interactive.CollectCnt,
	}
}
func (i *InteractiveMysqlDao) InterDomainToDao(interactive domain.Interactive) Interactive {
	return Interactive{
		ReadCnt:    interactive.ReadCnt,
		LikeCnt:    interactive.LikeCnt,
		CollectCnt: interactive.CollectCnt,
	}
}

type UserLikeBiz struct {
	Id     int64  `gorm:"primaryKey,autoIncrement"`
	Uid    int64  `gorm:"uniqueIndex:uid_biz_type_id"`
	BizId  int64  `gorm:"uniqueIndex:uid_biz_type_id"`
	Biz    string `gorm:"type:varchar(128);uniqueIndex:uid_biz_type_id"`
	Status int
	Utime  int64
	Ctime  int64
}

type UserCollectionBiz struct {
	Id int64 `gorm:"primaryKey,autoIncrement"`
	// 这边还是保留了了唯一索引
	Uid   int64  `gorm:"uniqueIndex:uid_biz_type_id"`
	BizId int64  `gorm:"uniqueIndex:uid_biz_type_id"`
	Biz   string `gorm:"type:varchar(128);uniqueIndex:uid_biz_type_id"`
	// 收藏夹的ID
	// 收藏夹ID本身有索引
	Cid   int64 `gorm:"index"`
	Utime int64
	Ctime int64
}
